/*
   Copyright 2018 Kai Huebl (kai@huebl-sgh.de)

   Lizenziert gemäß Apache Licence Version 2.0 (die „Lizenz“); Nutzung dieser
   Datei nur in Übereinstimmung mit der Lizenz erlaubt.
   Eine Kopie der Lizenz erhalten Sie auf http://www.apache.org/licenses/LICENSE-2.0.

   Sofern nicht gemäß geltendem Recht vorgeschrieben oder schriftlich vereinbart,
   erfolgt die Bereitstellung der im Rahmen der Lizenz verbreiteten Software OHNE
   GEWÄHR ODER VORBEHALTE – ganz gleich, ob ausdrücklich oder stillschweigend.

   Certificatermationen über die jeweiligen Bedingungen für Genehmigungen und Einschränkungen
   im Rahmen der Lizenz finden Sie in der Lizenz.

   Autor: Kai Huebl (kai@huebl-sgh.de)
 */

#ifndef __OpcUaStackCore_OpenSSLCompat_h__
#define __OpcUaStackCore_OpenSSLCompat_h__

#include <openssl/x509.h>
#include <openssl/opensslv.h>
#include <cstring>

namespace OpcUaStackCore
{

	#if OPENSSL_VERSION_NUMBER < 0x10100000L

#if 0
	static void *OPENSSL_zalloc(size_t num)
	{
	   void *ret = OPENSSL_malloc(num);

	   if (ret != NULL)
		   memset(ret, 0, num);
	   return ret;
	}

	int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d)
	{
	   /* If the fields n and e in r are NULL, the corresponding input
		* parameters MUST be non-NULL for n and e.  d may be
		* left NULL (in case only the public key is used).
		*/
	   if ((r->n == NULL && n == NULL)
		   || (r->e == NULL && e == NULL))
		   return 0;

	   if (n != NULL) {
		   BN_free(r->n);
		   r->n = n;
	   }
	   if (e != NULL) {
		   BN_free(r->e);
		   r->e = e;
	   }
	   if (d != NULL) {
		   BN_free(r->d);
		   r->d = d;
	   }

	   return 1;
	}

	int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q)
	{
	   /* If the fields p and q in r are NULL, the corresponding input
		* parameters MUST be non-NULL.
		*/
	   if ((r->p == NULL && p == NULL)
		   || (r->q == NULL && q == NULL))
		   return 0;

	   if (p != NULL) {
		   BN_free(r->p);
		   r->p = p;
	   }
	   if (q != NULL) {
		   BN_free(r->q);
		   r->q = q;
	   }

	   return 1;
	}

	int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp)
	{
	   /* If the fields dmp1, dmq1 and iqmp in r are NULL, the corresponding input
		* parameters MUST be non-NULL.
		*/
	   if ((r->dmp1 == NULL && dmp1 == NULL)
		   || (r->dmq1 == NULL && dmq1 == NULL)
		   || (r->iqmp == NULL && iqmp == NULL))
		   return 0;

	   if (dmp1 != NULL) {
		   BN_free(r->dmp1);
		   r->dmp1 = dmp1;
	   }
	   if (dmq1 != NULL) {
		   BN_free(r->dmq1);
		   r->dmq1 = dmq1;
	   }
	   if (iqmp != NULL) {
		   BN_free(r->iqmp);
		   r->iqmp = iqmp;
	   }

	   return 1;
	}

	void RSA_get0_key(const RSA *r,
					 const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
	{
	   if (n != NULL)
		   *n = r->n;
	   if (e != NULL)
		   *e = r->e;
	   if (d != NULL)
		   *d = r->d;
	}

	void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q)
	{
	   if (p != NULL)
		   *p = r->p;
	   if (q != NULL)
		   *q = r->q;
	}

	void RSA_get0_crt_params(const RSA *r,
							const BIGNUM **dmp1, const BIGNUM **dmq1,
							const BIGNUM **iqmp)
	{
	   if (dmp1 != NULL)
		   *dmp1 = r->dmp1;
	   if (dmq1 != NULL)
		   *dmq1 = r->dmq1;
	   if (iqmp != NULL)
		   *iqmp = r->iqmp;
	}

	void DSA_get0_pqg(const DSA *d,
					 const BIGNUM **p, const BIGNUM **q, const BIGNUM **g)
	{
	   if (p != NULL)
		   *p = d->p;
	   if (q != NULL)
		   *q = d->q;
	   if (g != NULL)
		   *g = d->g;
	}

	int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g)
	{
	   /* If the fields p, q and g in d are NULL, the corresponding input
		* parameters MUST be non-NULL.
		*/
	   if ((d->p == NULL && p == NULL)
		   || (d->q == NULL && q == NULL)
		   || (d->g == NULL && g == NULL))
		   return 0;

	   if (p != NULL) {
		   BN_free(d->p);
		   d->p = p;
	   }
	   if (q != NULL) {
		   BN_free(d->q);
		   d->q = q;
	   }
	   if (g != NULL) {
		   BN_free(d->g);
		   d->g = g;
	   }

	   return 1;
	}

	void DSA_get0_key(const DSA *d,
					 const BIGNUM **pub_key, const BIGNUM **priv_key)
	{
	   if (pub_key != NULL)
		   *pub_key = d->pub_key;
	   if (priv_key != NULL)
		   *priv_key = d->priv_key;
	}

	int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key)
	{
	   /* If the field pub_key in d is NULL, the corresponding input
		* parameters MUST be non-NULL.  The priv_key field may
		* be left NULL.
		*/
	   if (d->pub_key == NULL && pub_key == NULL)
		   return 0;

	   if (pub_key != NULL) {
		   BN_free(d->pub_key);
		   d->pub_key = pub_key;
	   }
	   if (priv_key != NULL) {
		   BN_free(d->priv_key);
		   d->priv_key = priv_key;
	   }

	   return 1;
	}

	void DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps)
	{
	   if (pr != NULL)
		   *pr = sig->r;
	   if (ps != NULL)
		   *ps = sig->s;
	}

	int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s)
	{
	   if (r == NULL || s == NULL)
		   return 0;
	   BN_clear_free(sig->r);
	   BN_clear_free(sig->s);
	   sig->r = r;
	   sig->s = s;
	   return 1;
	}

	void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps)
	{
	   if (pr != NULL)
		   *pr = sig->r;
	   if (ps != NULL)
		   *ps = sig->s;
	}

	int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s)
	{
	   if (r == NULL || s == NULL)
		   return 0;
	   BN_clear_free(sig->r);
	   BN_clear_free(sig->s);
	   sig->r = r;
	   sig->s = s;
	   return 1;
	}

	void DH_get0_pqg(const DH *dh,
					const BIGNUM **p, const BIGNUM **q, const BIGNUM **g)
	{
	   if (p != NULL)
		   *p = dh->p;
	   if (q != NULL)
		   *q = dh->q;
	   if (g != NULL)
		   *g = dh->g;
	}

	int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g)
	{
	   /* If the fields p and g in d are NULL, the corresponding input
		* parameters MUST be non-NULL.  q may remain NULL.
		*/
	   if ((dh->p == NULL && p == NULL)
		   || (dh->g == NULL && g == NULL))
		   return 0;

	   if (p != NULL) {
		   BN_free(dh->p);
		   dh->p = p;
	   }
	   if (q != NULL) {
		   BN_free(dh->q);
		   dh->q = q;
	   }
	   if (g != NULL) {
		   BN_free(dh->g);
		   dh->g = g;
	   }

	   if (q != NULL) {
		   dh->length = BN_num_bits(q);
	   }

	   return 1;
	}

	void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key)
	{
	   if (pub_key != NULL)
		   *pub_key = dh->pub_key;
	   if (priv_key != NULL)
		   *priv_key = dh->priv_key;
	}

	int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key)
	{
	   /* If the field pub_key in dh is NULL, the corresponding input
		* parameters MUST be non-NULL.  The priv_key field may
		* be left NULL.
		*/
	   if (dh->pub_key == NULL && pub_key == NULL)
		   return 0;

	   if (pub_key != NULL) {
		   BN_free(dh->pub_key);
		   dh->pub_key = pub_key;
	   }
	   if (priv_key != NULL) {
		   BN_free(dh->priv_key);
		   dh->priv_key = priv_key;
	   }

	   return 1;
	}

	int DH_set_length(DH *dh, long length)
	{
	   dh->length = length;
	   return 1;
	}

	const unsigned char *EVP_CIPHER_CTX_iv(const EVP_CIPHER_CTX *ctx)
	{
	   return ctx->iv;
	}

	unsigned char *EVP_CIPHER_CTX_iv_noconst(EVP_CIPHER_CTX *ctx)
	{
	   return ctx->iv;
	}

	EVP_MD_CTX *EVP_MD_CTX_new(void)
	{
	   return (EVP_MD_CTX*)OPENSSL_zalloc(sizeof(EVP_MD_CTX));
	}

	void EVP_MD_CTX_free(EVP_MD_CTX *ctx)
	{
	   EVP_MD_CTX_cleanup(ctx);
	   OPENSSL_free(ctx);
	}

	RSA_METHOD *RSA_meth_dup(const RSA_METHOD *meth)
	{
	   RSA_METHOD *ret;

	   ret = (RSA_METHOD*)OPENSSL_malloc(sizeof(RSA_METHOD));

	   if (ret != NULL) {
		   memcpy(ret, meth, sizeof(*meth));
		   ret->name = OPENSSL_strdup(meth->name);
		   if (ret->name == NULL) {
			   OPENSSL_free(ret);
			   return NULL;
		   }
	   }

	   return ret;
	}

	int RSA_meth_set1_name(RSA_METHOD *meth, const char *name)
	{
	   char *tmpname;

	   tmpname = OPENSSL_strdup(name);
	   if (tmpname == NULL) {
		   return 0;
	   }

	   OPENSSL_free((char *)meth->name);
	   meth->name = tmpname;

	   return 1;
	}

	int RSA_meth_set_priv_enc(RSA_METHOD *meth,
							 int (*priv_enc) (int flen, const unsigned char *from,
											  unsigned char *to, RSA *rsa,
											  int padding))
	{
	   meth->rsa_priv_enc = priv_enc;
	   return 1;
	}

	int RSA_meth_set_priv_dec(RSA_METHOD *meth,
							 int (*priv_dec) (int flen, const unsigned char *from,
											  unsigned char *to, RSA *rsa,
											  int padding))
	{
	   meth->rsa_priv_dec = priv_dec;
	   return 1;
	}

	int RSA_meth_set_finish(RSA_METHOD *meth, int (*finish) (RSA *rsa))
	{
	   meth->finish = finish;
	   return 1;
	}

	void RSA_meth_free(RSA_METHOD *meth)
	{
	   if (meth != NULL) {
		   OPENSSL_free((char *)meth->name);
		   OPENSSL_free(meth);
	   }
	}

	int RSA_bits(const RSA *r)
	{
	   return (BN_num_bits(r->n));
	}
#endif

	RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey);
	int EVP_PKEY_up_ref(EVP_PKEY *pkey);

	#endif

}

#endif
